#include "plugin_control.h"

static struct plugin_control *plugin_control_t = NULL;
static void insert_to_plugin_list( struct plugin *plugin );	/*TODO: some associative data structure could be used */
static void insert_to_plugin_table_list( struct plugin_table *entry ); /* to combine these... */
//static void free_plugin_table_list_entries( struct list *plugin_table_list );
static void handle_notification( char *msg );
static int load_plugin( char *filename );
static int unload_plugin( char *filename );

static void handle_notification( char *msg ) 
{
	char *part;

	if ( !strncmp( msg, "/quit", 5 ) ) 
			plugin_control_t->notify_core( msg );
	else if ( !strncmp( msg, "/load", 5 ) ) {
		part = strtok( msg, " " );
		part = strtok( NULL, " " );
		load_plugin( part );
	}
	else if ( !strncmp( msg, "/unload", 7 ) ) {
		part = strtok( msg, " " );
		part = strtok( NULL, " " );
		unload_plugin( part );
	}
}

static int load_plugin( char *filename ) 
{
	void *handle;
  char *error;
	struct plugin * (*new_plugin)();
	struct plugin *plugin = NULL;

	printf("Try to load %s plugin. \n", filename);

	handle = dlopen( filename, RTLD_LAZY );
	if ( !handle ) {
		fprintf( stderr, "Failed to load %s", dlerror() );		
		return -1;
	}

	new_plugin = dlsym( handle, "new_plugin" );
  if ( (error = dlerror()) != NULL ) {
		fprintf( stderr, "Failed to resolve function - %s. \n", dlerror() );		
		return -1;
  }

	plugin = (*new_plugin)();
	if ( plugin == NULL ) {
		fprintf(stderr, "Failed to create plugin.\n");
		return -1;
	}

	insert_to_plugin_list( plugin );

	plugin->send_message = plugin_control_t->send_message;

	struct plugin_table *plugin_table_t = ( struct plugin_table *) malloc( sizeof( struct plugin_table ) );
	/*TODO: check allocation above */
	plugin_table_t->plugin_id = plugin->id;
	plugin_table_t->filename = (char*)malloc( strlen(filename)+1 ); /*remember to free this somewhere*/
	strcpy( plugin_table_t->filename, filename );
	//memcpy( plugin_table_t->filename, filename, strlen(filename) );
	//plugin_table_t->filename = filename;
	plugin_table_t->handle = handle;

	//printf("plugin_table_t->plugin_id = %s\n", plugin_table_t->plugin_id );
	//printf("plugin_table_t->filename = %s\n", plugin_table_t->filename );

	insert_to_plugin_table_list( plugin_table_t );

	printf("%s loaded successfully.\n", plugin->id );
		
	return 0;
}

static int unload_plugin( char *filename ) 
{
	struct list_node *node_plugin;
	struct list_node *node_plugin_table;
	struct plugin *plugin;
	struct plugin_table *plugin_table;

	node_plugin_table = plugin_control_t->plugin_table_list->head;
	while (	node_plugin_table != NULL ) {

		plugin_table = (struct plugin_table*)node_plugin_table->data;
		if ( plugin_table == NULL ){
			printf("PLUGIN_TABLE NULL \n");
			return -2;
		}

		if ( strcmp( filename, plugin_table->filename ) == 0 ) 
			break;

		node_plugin_table = node_plugin_table->next;
	}

	//printf("plugin_table->plugin_id = %s\n", plugin_table->plugin_id );
	//printf("plugin_table->handle = %d\n", plugin_table->handle );
	//printf("node_plugin_table = %d\n", node_plugin_table );

	if ( node_plugin_table == NULL ) {
		fprintf( stderr, "Could not find plugin table with filename = %s\n", filename );
		return -1;
	}

	node_plugin = plugin_control_t->plugin_list->head;
	while (	node_plugin != NULL ) {

		plugin = (struct plugin*)node_plugin->data;
		if ( plugin == NULL ) {
			printf("PLUGIN NULL \n");
			return -2;
		}

		if ( strcmp( plugin_table->plugin_id, plugin->id ) == 0 ) 
			break;

		node_plugin = node_plugin->next;
	}

	printf("plugin found -> %s\n", plugin->id );

	dlclose( plugin_table->handle );

	//free( node_plugin_table->data );
	remove_from_list( plugin_control_t->plugin_table_list, node_plugin_table );

	if ( node_plugin == NULL ) {
		fprintf( stderr, "Could not find plugin with filename = %s\n", filename );
		return -1;
	}

	//free( node_plugin->data );
	remove_from_list( plugin_control_t->plugin_list, node_plugin );

	printf("%s has been unload successfully.\n", filename );

	/*TODO: check that all allocated memory will be freeded */

	return 0;
}

struct plugin_control * new_plugin_control( void *notify_core ) 
{
	plugin_control_t = ( struct plugin_control *) malloc( sizeof( struct plugin_control ) );

	if ( plugin_control_t == NULL ) {
		fprintf( stderr, "List allocation failed, out of memory. \n" );		
		return NULL;
	}

	plugin_control_t->core_plugin_t = new_core_plugin();
	if ( plugin_control_t->core_plugin_t == NULL ) {
		fprintf(stderr, "Failed to create core plugin.\n");
		return NULL;
	}

	plugin_control_t->notify_core = notify_core;
	plugin_control_t->core_plugin_t->notify_plugin_control = handle_notification;

	plugin_control_t->plugin_list = new_list();
	if ( plugin_control_t->plugin_list == NULL ) {
		fprintf(stderr, "Failed to create plugin list.\n");
		return NULL;
	}

	insert_to_plugin_list( plugin_control_t->core_plugin_t->plugin );

	plugin_control_t->plugin_table_list = new_list();
	if ( plugin_control_t->plugin_table_list == NULL ) {
		fprintf(stderr, "Failed to create plugin table list.\n");
		return NULL;
	}

	return plugin_control_t;
}

void delete_plugin_control()
{
	delete_core_plugin();

	if ( plugin_control_t != NULL ) { 
		delete_list( plugin_control_t->plugin_list );
		//free_plugin_table_list_entries( plugin_control_t->plugin_table_list );
		delete_list( plugin_control_t->plugin_table_list );
		free( plugin_control_t ); 
	}
}

static void insert_to_plugin_list( struct plugin *plugin ) 
{
	insert_to_list( plugin_control_t->plugin_list, plugin );
}

static void insert_to_plugin_table_list( struct plugin_table *entry ) 
{
	insert_to_list( plugin_control_t->plugin_table_list, entry );
}

/*
static void free_plugin_table_list_entries( struct list *plugin_table_list ) 
{
	struct list_node *node;

	node = plugin_table_list->head;
	while (	node != NULL ) 
		free( node->data );

}*/

